// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ConnectionManager.cs" company="">
//   
// </copyright>
// <summary>
//   Defines the ConnectionManager type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ComparisonTool.Connection
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.IO;
    using System.Linq;
    using System.Net;
    using System.Windows.Forms;

    using ComparisonTool.Forms.Interfaces;

    using Microsoft.Crm.Sdk.Messages;
    using Microsoft.Xrm.Client;
    using Microsoft.Xrm.Client.Services;
    using Microsoft.Xrm.Sdk;

    /// <summary>
    /// The connection manager.
    /// </summary>
    public class ConnectionManager
    {
        /// <summary>
        /// The config file name.
        /// </summary>
        private const string ConfigFileName = "mscrmtools2011.config";

        /// <summary>
        /// The crypto pass phrase.
        /// </summary>
        private const string CryptoPassPhrase = "MsCrmTools";

        /// <summary>
        /// The crypto salt value.
        /// </summary>
        private const string CryptoSaltValue = "Sergii 89*";

        /// <summary>
        /// The crypto vector.
        /// </summary>
        private const string CryptoInitVector = "ahC3@bCa2Didfc3d";

        /// <summary>
        /// The crypto hash.
        /// </summary>
        private const string CryptoHashAlgorythm = "SHA1";

        /// <summary>
        /// The crypto password iterations.
        /// </summary>
        private const int CryptoPasswordIterations = 2;

        /// <summary>
        /// The crypto key size.
        /// </summary>
        private const int CryptoKeySize = 256;

        /// <summary>
        /// The _form helper.
        /// </summary>
        private readonly FormHelper formHelper;

        /// <summary>
        /// The view.
        /// </summary>
        private readonly IMainForm view;

        /// <summary>
        /// Initializes a new instance of the <see cref="ConnectionManager"/> class.
        /// </summary>
        /// <param name="view">
        /// The view.
        /// </param>
        public ConnectionManager(IMainForm view)
        {
            this.formHelper = new FormHelper((Form)view.GetMainForm(), this);
            this.view = view;
            this.ConnectionsList = this.LoadConnectionsList();

            for (var i = 1; i <= 2; i++)
            {
                view.SetStatusConnection("Not connected.", i);
                this.AddActionsList(view.GetConnectControl(i));
            }
        }

        /// <summary>
        /// The connection succeed event handler.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        public delegate void ConnectionSucceedEventHandler(object sender, ConnectionSucceedEventArgs e);

        /// <summary>
        /// The connection failed event handler.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        public delegate void ConnectionFailedEventHandler(object sender, ConnectionFailedEventArgs e);

        /// <summary>
        /// The connection succeed.
        /// </summary>
        public event ConnectionSucceedEventHandler ConnectionSucceed;

        /// <summary>
        /// The connection succeed.
        /// </summary>
        public event ConnectionFailedEventHandler ConnectionFailed;

        /// <summary>
        /// Gets or sets the connections list.
        /// </summary>
        public CrmConnections ConnectionsList { get; set; }

        /// <summary>
        /// The load connections list.
        /// </summary>
        /// <returns>
        /// The <see cref="CrmConnections"/>.
        /// </returns>
        public CrmConnections LoadConnectionsList()
        {
            try
            {
                CrmConnections crmConnections;
                if (File.Exists(ConfigFileName))
                {
                    using (var configReader = new StreamReader(ConfigFileName))
                    {
                        crmConnections = (CrmConnections)XmlSerializerHelper.Deserialize(configReader.ReadToEnd(), typeof(CrmConnections));
                    }

                    if (!string.IsNullOrEmpty(crmConnections.Password))
                    {
                        crmConnections.Password = CryptoManager.Decrypt(
                            crmConnections.Password, 
                            CryptoPassPhrase, 
                            CryptoSaltValue, 
                            CryptoHashAlgorythm, 
                            CryptoPasswordIterations, 
                            CryptoInitVector, 
                            CryptoKeySize);
                    }

                    foreach (var detail in crmConnections.Connections)
                    {
                        if (!string.IsNullOrEmpty(detail.UserPassword))
                        {
                            detail.UserPassword = CryptoManager.Decrypt(
                                                                        detail.UserPassword, 
                                                                        CryptoPassPhrase, 
                                                                        CryptoSaltValue, 
                                                                        CryptoHashAlgorythm, 
                                                                        CryptoPasswordIterations, 
                                                                        CryptoInitVector, 
                                                                        CryptoKeySize);
                        }

                        // Fix for new connection code
                        if (string.IsNullOrEmpty(detail.OrganizationUrlName))
                        {
                            if (detail.UseIfd || detail.UseOnline || detail.UseOsdp)
                            {
                                var uri = new Uri(detail.OrganizationServiceUrl);
                                detail.OrganizationUrlName = uri.Host.Split('.')[0];
                            }
                            else
                            {
                                detail.OrganizationUrlName = detail.Organization;
                            }
                        }
                    }
                }
                else
                {
                    crmConnections = new CrmConnections
                    {
                        Connections = new List<ConnectionDetail>()
                    };
                }

                return crmConnections;
            }
            catch (Exception error)
            {
                throw new Exception("Error while deserializing configuration file. Details: " + error.Message);
            }
        }

        /// <summary>
        /// The connect to server.
        /// </summary>
        /// <param name="detail">
        /// The detail.
        /// </param>
        /// <param name="id">
        /// The id.
        /// </param>
        public void ConnectToServer(ConnectionDetail detail, int id)
        {
            this.ConnectToServer(detail, id, null);
        }

        /// <summary>
        /// The connect to server.
        /// </summary>
        /// <param name="detail">
        /// The detail.
        /// </param>
        /// <param name="id">
        /// The id.
        /// </param>
        /// <param name="connectionParameter">
        /// The connection parameter.
        /// </param>
        public void ConnectToServer(ConnectionDetail detail, int id, object connectionParameter)
        {
            var parameters = new List<object> { detail, connectionParameter, id };

            // Runs the connection asynchronously
            var worker = new BackgroundWorker();
            worker.DoWork += this.WorkerDoWork;
            worker.RunWorkerCompleted += this.WorkerRunWorkerCompleted;
            worker.RunWorkerAsync(parameters);
        }

        /// <summary>
        /// The rebuild connection list.
        /// </summary>
        public void RebuildConnectionList()
        {
            this.AddActionsList(this.view.GetConnectControl(1));
            this.AddActionsList(this.view.GetConnectControl(2));
        }

        /// <summary>
        /// The test connection.
        /// </summary>
        /// <param name="service">
        /// The service.
        /// </param>
        /// <param name="id">
        /// The id.
        /// </param>
        /// <exception cref="Exception">
        /// Exception message.
        /// </exception>
        public void TestConnection(IOrganizationService service, int id)
        {
            try
            {
                this.view.SetStatusConnection("Testing connection...", id);

                var request = new WhoAmIRequest();
                service.Execute(request);
            }
            catch (Exception error)
            {
                throw new Exception("Test connection failed: " + error.Message);
            }
        }

        /// <summary>
        /// The set connection status.
        /// </summary>
        /// <param name="isConnected">
        /// The is connected.
        /// </param>
        /// <param name="detail">
        /// The detail.
        /// </param>
        /// <param name="id">
        /// The id.
        /// </param>
        public void SetConnectionStatus(bool isConnected, ConnectionDetail detail, int id)
        {
            if (isConnected)
            {
                var btn = this.view.GetConnectControl(id);
                btn.Text = string.Format("Connected to '{0} ({1})'", detail.ServerName, detail.OrganizationFriendlyName);

                // btn.Image = (System.Drawing.Image)this.resources.GetObject("server_lightning");
            }
            else
            {

                this.view.SetStatusConnection(@"Not connected", id);

                // btn.Image = (System.Drawing.Image)this.resources.GetObject("server");
            }
        }

        /// <summary>
        /// The save connections file.
        /// </summary>
        /// <param name="connectionsList">
        /// The connections list.
        /// </param>
        public void SaveConnectionsFile(CrmConnections connectionsList)
        {
            if (!string.IsNullOrEmpty(connectionsList.Password))
            {
                connectionsList.Password = CryptoManager.Encrypt(
                                                                connectionsList.Password, 
                                                                CryptoPassPhrase, 
                                                                CryptoSaltValue, 
                                                                CryptoHashAlgorythm, 
                                                                CryptoPasswordIterations, 
                                                                CryptoInitVector, 
                                                                CryptoKeySize);
            }

            var cache = new Dictionary<Guid, string>();

            foreach (var detail in connectionsList.Connections)
            {
                if (!detail.ConnectionId.HasValue)
                {
                    continue;
                }

                cache.Add(detail.ConnectionId.Value, detail.UserPassword);

                if (detail.SavePassword)
                {
                    if (!string.IsNullOrEmpty(detail.UserPassword))
                    {
                        detail.UserPassword = CryptoManager.Encrypt(
                                                                    detail.UserPassword, 
                                                                    CryptoPassPhrase, 
                                                                    CryptoSaltValue, 
                                                                    CryptoHashAlgorythm, 
                                                                    CryptoPasswordIterations, 
                                                                    CryptoInitVector, 
                                                                    CryptoKeySize);
                    }
                }
                else
                {
                    detail.UserPassword = null;
                }
            }

            XmlSerializerHelper.SerializeToFile(connectionsList, ConfigFileName);

            foreach (var detail in connectionsList.Connections)
            {
                if (!detail.ConnectionId.HasValue)
                {
                    continue;
                }

                if (detail.UserPassword == null)
                {
                    detail.UserPassword = cache[detail.ConnectionId.Value];
                    continue;
                }

                if (!string.IsNullOrEmpty(detail.UserPassword))
                {
                    detail.UserPassword = CryptoManager.Decrypt(
                                                                detail.UserPassword, 
                                                                CryptoPassPhrase, 
                                                                CryptoSaltValue, 
                                                                CryptoHashAlgorythm, 
                                                                CryptoPasswordIterations, 
                                                                CryptoInitVector, 
                                                                CryptoKeySize);
                }
            }
        }

        /// <summary>
        /// The add actions list.
        /// </summary>
        /// <param name="btn">
        /// The button.
        /// </param>
        private void AddActionsList(ToolStripDropDownItem btn)
        {
            // Clearing existing connections
            btn.DropDownItems.Clear();
            var id = btn.Tag;

            if (this.ConnectionsList != null && this.ConnectionsList.Connections.Count > 0)
            {
                this.ConnectionsList.Connections.Sort();

                foreach (var connectionDetail in this.ConnectionsList.Connections)
                {
                    var item = new ToolStripMenuItem
                                   {
                                       Text = connectionDetail.ConnectionName, 
                                       Tag = connectionDetail
                                   };

                    // if (connectionDetail.UseOnline)
                    // {
                    // // item.Image = ResourceManager.GetImage("McTools.Xrm.Connection.WinForms.Resources.CRMOnlineLive_16.png");
                    // }
                    // else if (connectionDetail.UseOsdp)
                    // {
                    // // item.Image = ResourceManager.GetImage("McTools.Xrm.Connection.WinForms.Resources.CRMOnlineLive_16.png");
                    // }
                    // else if (connectionDetail.UseIfd)
                    // {
                    // // item.Image = ResourceManager.GetImage("McTools.Xrm.Connection.WinForms.Resources.server_key.png");
                    // }
                    // else
                    // {
                    // // item.Image = ResourceManager.GetImage("McTools.Xrm.Connection.WinForms.Resources.server.png");
                    // }
                    this.BuildActionItems(item);

                    btn.DropDownItems.Add(item);
                }

                if (this.ConnectionsList.Connections.Count > 0)
                {
                    var separator = new ToolStripSeparator();
                    btn.DropDownItems.Add(separator);
                }
            }

            var newConnectionItem = new ToolStripMenuItem { Text = @"Create new connection", Tag = id };

            // newConnectionItem.Image = (System.Drawing.Image)this.resources.GetObject("server_add");
            newConnectionItem.Click += this.NewConnectionItemClick;
            btn.DropDownItems.Add(newConnectionItem);
        }

        /// <summary>
        /// The new connection item_ click.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        private void NewConnectionItemClick(object sender, EventArgs e)
        {
            var btn = sender as ToolStripMenuItem;
            var id = "1";
            if (btn != null)
            {
                id = (sender as ToolStripMenuItem).Tag.ToString();
            }

            var detail = this.formHelper.EditConnection(true, null, Convert.ToInt32(id)); // Correct id

            if (detail != null)
            {
                var item = new ToolStripMenuItem { Text = detail.ConnectionName, Tag = detail };

                this.BuildActionItems(item);

                // var connexionManager =  (ToolStripDropDownButton)this.Items[0];

                // if (connexionManager.DropDownItems.Count == 1)
                // {
                // connexionManager.DropDownItems.Insert(0, new ToolStripSeparator());
                // connexionManager.DropDownItems.Insert(0, item);
                // }
                // else
                // {
                // connexionManager.DropDownItems.Insert(connexionManager.DropDownItems.Count - 2, item);
                // }

                // MessageBox.Show(this, @"Connection Created Successfully!", @"Information", MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
        }

        /// <summary>
        /// Creates the three action menus for a connection
        /// </summary>
        /// <param name="item">
        /// Menu where to add the actions
        /// </param>
        private void BuildActionItems(ToolStripMenuItem item)
        {
            var connectItem = new ToolStripMenuItem();
            connectItem.Click += this.ActionItemClick;
            connectItem.Text = @"Connect";

            // connectItem.Image = (System.Drawing.Image)this.resources.GetObject("server_connect");
            item.DropDownItems.Add(connectItem);

            var editItem = new ToolStripMenuItem();
            editItem.Click += this.ActionItemClick;
            editItem.Text = @"Edit";

            // editItem.Image = (System.Drawing.Image)this.resources.GetObject("server_edit");
            item.DropDownItems.Add(editItem);

            var deleteItem = new ToolStripMenuItem();
            deleteItem.Click += this.ActionItemClick;
            deleteItem.Text = @"Delete";

            // deleteItem.Image = (System.Drawing.Image)this.resources.GetObject("server_delete");
            item.DropDownItems.Add(deleteItem);
        }

        /// <summary>
        /// The action item_ click.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        private void ActionItemClick(object sender, EventArgs e)
        {
            var clickedItem = (ToolStripMenuItem)sender;
            var parentItem = (ToolStripMenuItem)clickedItem.OwnerItem;
            var currentConnection = (ConnectionDetail)parentItem.Tag;
            var connexionManager = (ToolStripDropDownButton)parentItem.OwnerItem;
            var id = Convert.ToInt32(connexionManager.Tag);

            switch (clickedItem.Text)
            {
                case "Connect":
                    this.ConnectToServer(currentConnection, id);

                    break;

                case "Edit":
                    currentConnection = this.formHelper.EditConnection(false, currentConnection, id);
                    if (currentConnection != null && parentItem.Text != currentConnection.ConnectionName)
                    {
                        parentItem.Text = currentConnection.ConnectionName;
                        parentItem.Tag = currentConnection;
                    }

                    break;

                case "Delete":
                    connexionManager.DropDownItems.Remove(parentItem);

                    if (connexionManager.DropDownItems.Count == 2)
                    {
                        connexionManager.DropDownItems.RemoveAt(0);
                    }

                    this.formHelper.DeleteConnection(currentConnection, id);

                    break;
            }
        }

        /// <summary>
        /// The worker run worker completed.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        private void WorkerRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            var parameters = (List<object>)e.Result;

            if (parameters.Count == 4)
            {
                var error = parameters[3] as Exception;
                if (error != null)
                {
                    this.SendFailureMessage(error.Message, parameters);
                }
                else
                {
                    var service = parameters[3] as IOrganizationService;
                    if (service != null)
                    {
                        this.SendSuccessMessage(service, parameters);
                    }
                }
            }
        }

        /// <summary>
        /// The send failure message.
        /// </summary>
        /// <param name="failureReason">
        /// The failure reason.
        /// </param>
        /// <param name="parameters">
        /// </param>
        private void SendFailureMessage(string failureReason, List<object> parameters)
        {
            if (this.ConnectionFailed != null)
            {
                var args = new ConnectionFailedEventArgs
                {
                    FailureReason = failureReason, 
                    Id = (int)parameters[2]
                };

                this.ConnectionFailed(this, args);
            }
        }

        /// <summary>
        /// The send success message.
        /// </summary>
        /// <param name="service">
        /// The service.
        /// </param>
        /// <param name="parameters">
        /// The parameters.
        /// </param>
        private void SendSuccessMessage(IOrganizationService service, IList<object> parameters)
        {
            if (this.ConnectionSucceed != null)
            {
                var args = new ConnectionSucceedEventArgs
                {
                    OrganizationService = service, 
                    ConnectionDetail = (ConnectionDetail)parameters[0], 
                    Parameter = parameters[1], 
                    Id = (int)parameters[2]
                };

                this.ConnectionSucceed(this, args);
            }
        }

        /// <summary>
        /// The worker do work.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        private void WorkerDoWork(object sender, DoWorkEventArgs e)
        {
            var result = this.Connect((List<object>)e.Argument);
            e.Result = e.Argument;
            ((List<object>)e.Result).Add(result);
        }

        /// <summary>
        /// The connect.
        /// </summary>
        /// <param name="parameters">
        /// The parameters.
        /// </param>
        /// <returns>
        /// The <see cref="object"/>.
        /// </returns>
        private object Connect(IList<object> parameters)
        {
            WebRequest.DefaultWebProxy = WebRequest.GetSystemWebProxy();

            var detail = (ConnectionDetail)parameters[0];
            var id = (int)parameters[2];

            this.view.SetStatusConnection("Creating Organization service proxy...", id);


            // Connecting to Crm server
            try
            {
                var connection = CrmConnection.Parse(detail.GetOrganizationCrmConnectionString());

                var service = new OrganizationService(connection);

                this.TestConnection(service, id);

                var currentConnection = this.ConnectionsList.Connections.FirstOrDefault(x => x.ConnectionId == detail.ConnectionId);
                if (currentConnection != null)
                {
                    currentConnection.SavePassword = detail.SavePassword;
                    currentConnection.UserPassword = detail.UserPassword;
                }

                this.SaveConnectionsFile(this.ConnectionsList);

                return service;
            }
            catch (Exception error)
            {
                return error;
            }
        }
    }
}
